/*
 * This file is part of Cleanflight and Betaflight.
 *
 * Cleanflight and Betaflight are free software. You can redistribute
 * this software and/or modify this software under the terms of the
 * GNU General Public License as published by the Free Software
 * Foundation, either version 3 of the License, or (at your option)
 * any later version.
 *
 * Cleanflight and Betaflight are distributed in the hope that they
 * will be useful, but WITHOUT ANY WARRANTY; without even the implied
 * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 * See the GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this software.
 *
 * If not, see <http://www.gnu.org/licenses/>.
 *
 * Author: Dominic Clifton
 */

#include <stdbool.h>
#include <stdint.h>

#include "platform.h"

#ifdef USE_QUADSPI

#include "drivers/bus_quadspi.h"
#include "drivers/bus_quadspi_impl.h"
#include "drivers/dma.h"
#include "drivers/io.h"
#include "drivers/io_impl.h"
#include "drivers/nvic.h"
#include "platform/rcc.h"

#include "pg/bus_quadspi.h"
#include "drivers/bus_quadspi_impl.h"
// Provide platform-specific hardware table for STM32
const quadSpiHardware_t quadSpiHardware[QUADSPIDEV_COUNT] = {
#ifdef STM32H7
    {
        .device = QUADSPIDEV_1,
        .reg = QUADSPI,
        .clkPins = {
            { DEFIO_TAG_E(PB2),  GPIO_AF9_QUADSPI },
        },
        .bk1IO0Pins = {
            { DEFIO_TAG_E(PC9),  GPIO_AF9_QUADSPI },
            { DEFIO_TAG_E(PD11), GPIO_AF9_QUADSPI },
            { DEFIO_TAG_E(PF8),  GPIO_AF10_QUADSPI },
        },
        .bk1IO1Pins = {
            { DEFIO_TAG_E(PC10), GPIO_AF9_QUADSPI },
            { DEFIO_TAG_E(PD12), GPIO_AF9_QUADSPI },
            { DEFIO_TAG_E(PF9),  GPIO_AF10_QUADSPI },
        },
        .bk1IO2Pins = {
            { DEFIO_TAG_E(PE2),  GPIO_AF9_QUADSPI },
            { DEFIO_TAG_E(PF7),  GPIO_AF9_QUADSPI },
        },
        .bk1IO3Pins = {
            { DEFIO_TAG_E(PA1),  GPIO_AF9_QUADSPI },
            { DEFIO_TAG_E(PD13), GPIO_AF9_QUADSPI },
            { DEFIO_TAG_E(PF6),  GPIO_AF9_QUADSPI },
        },
        .bk1CSPins = {
            { DEFIO_TAG_E(PB6),  GPIO_AF10_QUADSPI },
            { DEFIO_TAG_E(PB10), GPIO_AF9_QUADSPI },
            { DEFIO_TAG_E(PG6),  GPIO_AF10_QUADSPI },
        },
        .bk2IO0Pins = {
            { DEFIO_TAG_E(PE7),  GPIO_AF10_QUADSPI },
            //{ DEFIO_TAG_E(PH7),  GPIO_AF9_QUADSPI }, // FIXME regenerate io_def_generated with support for GPIO 'H'
        },
        .bk2IO1Pins = {
            { DEFIO_TAG_E(PE8),  GPIO_AF10_QUADSPI },
            //{ DEFIO_TAG_E(PH3),  GPIO_AF9_QUADSPI }, // FIXME regenerate io_def_generated with support for GPIO 'H'
        },
        .bk2IO2Pins = {
            { DEFIO_TAG_E(PE9),  GPIO_AF10_QUADSPI },
            { DEFIO_TAG_E(PG9),  GPIO_AF9_QUADSPI },
        },
        .bk2IO3Pins = {
            { DEFIO_TAG_E(PE10),  GPIO_AF10_QUADSPI },
            { DEFIO_TAG_E(PG14),  GPIO_AF9_QUADSPI },
        },
        .bk2CSPins = {
            { DEFIO_TAG_E(PC11),  GPIO_AF9_QUADSPI },
        },
        .rcc = RCC_AHB3(QSPI),
    },
#endif
#ifdef STM32G4
    {
        .device = QUADSPIDEV_1,
        .reg = QUADSPI,
        .clkPins = {
            { DEFIO_TAG_E(PA3), GPIO_AF10_QUADSPI },
            { DEFIO_TAG_E(PB10), GPIO_AF10_QUADSPI },
            { DEFIO_TAG_E(PE10), GPIO_AF10_QUADSPI },
            { DEFIO_TAG_E(PF10), GPIO_AF10_QUADSPI },
        },
        .bk1IO0Pins = {
            { DEFIO_TAG_E(PB1), GPIO_AF10_QUADSPI },
            { DEFIO_TAG_E(PE12), GPIO_AF10_QUADSPI },
            { DEFIO_TAG_E(PF8), GPIO_AF10_QUADSPI },
        },
        .bk1IO1Pins = {
            { DEFIO_TAG_E(PB0), GPIO_AF10_QUADSPI },
            { DEFIO_TAG_E(PE13), GPIO_AF10_QUADSPI },
            { DEFIO_TAG_E(PF9), GPIO_AF10_QUADSPI },
        },
        .bk1IO2Pins = {
            { DEFIO_TAG_E(PA7), GPIO_AF10_QUADSPI },
            { DEFIO_TAG_E(PE14), GPIO_AF10_QUADSPI },
            { DEFIO_TAG_E(PF7), GPIO_AF10_QUADSPI },
        },
        .bk1IO3Pins = {
            { DEFIO_TAG_E(PA6), GPIO_AF10_QUADSPI },
            { DEFIO_TAG_E(PE15), GPIO_AF10_QUADSPI },
            { DEFIO_TAG_E(PF6), GPIO_AF10_QUADSPI },
        },
        .bk1CSPins = {
            { DEFIO_TAG_E(PA2), GPIO_AF10_QUADSPI },
            { DEFIO_TAG_E(PB11), GPIO_AF10_QUADSPI },
            { DEFIO_TAG_E(PE11), GPIO_AF10_QUADSPI },
        },
        .bk2IO0Pins = {
            { DEFIO_TAG_E(PC1), GPIO_AF10_QUADSPI },
            { DEFIO_TAG_E(PD4), GPIO_AF10_QUADSPI },
        },
        .bk2IO1Pins = {
            { DEFIO_TAG_E(PB2), GPIO_AF10_QUADSPI },
            { DEFIO_TAG_E(PC2), GPIO_AF10_QUADSPI },
            { DEFIO_TAG_E(PD5), GPIO_AF10_QUADSPI },
        },
        .bk2IO2Pins = {
            { DEFIO_TAG_E(PC3), GPIO_AF10_QUADSPI },
            { DEFIO_TAG_E(PD6), GPIO_AF10_QUADSPI },
        },
        .bk2IO3Pins = {
            { DEFIO_TAG_E(PC4), GPIO_AF10_QUADSPI },
            { DEFIO_TAG_E(PD7), GPIO_AF10_QUADSPI },
        },
        .bk2CSPins = {
            { DEFIO_TAG_E(PD3), GPIO_AF10_QUADSPI },
        },
        .rcc = RCC_AHB3(QSPI),
    },
#endif
};

// Provide platform-specific pin configuration for STM32
void quadSpiPinConfigure(const quadSpiConfig_t *pConfig)
{
    for (size_t hwindex = 0; hwindex < ARRAYLEN(quadSpiHardware); hwindex++) {
        const quadSpiHardware_t *hw = &quadSpiHardware[hwindex];

        if (!hw->reg) {
            continue;
        }

        quadSpiDevice_e device = hw->device;
        quadSpiDevice_t *pDev = &quadSpiDevice[device];

        for (int pindex = 0; pindex < MAX_QUADSPI_PIN_SEL; pindex++) {
            if (pConfig[device].ioTagClk == hw->clkPins[pindex].pin) {
                pDev->clk = hw->clkPins[pindex].pin;
                pDev->clkAF = hw->clkPins[pindex].af;
            }
            //
            // BK1
            //
            if (pConfig[device].ioTagBK1IO0 == hw->bk1IO0Pins[pindex].pin) {
                pDev->bk1IO0 = hw->bk1IO0Pins[pindex].pin;
                pDev->bk1IO0AF = hw->bk1IO0Pins[pindex].af;
            }
            if (pConfig[device].ioTagBK1IO1 == hw->bk1IO1Pins[pindex].pin) {
                pDev->bk1IO1 = hw->bk1IO1Pins[pindex].pin;
                pDev->bk1IO1AF = hw->bk1IO1Pins[pindex].af;
            }
            if (pConfig[device].ioTagBK1IO2 == hw->bk1IO2Pins[pindex].pin) {
                pDev->bk1IO2 = hw->bk1IO2Pins[pindex].pin;
                pDev->bk1IO2AF = hw->bk1IO2Pins[pindex].af;
            }
            if (pConfig[device].ioTagBK1IO3 == hw->bk1IO3Pins[pindex].pin) {
                pDev->bk1IO3 = hw->bk1IO3Pins[pindex].pin;
                pDev->bk1IO3AF = hw->bk1IO3Pins[pindex].af;
            }
            if (pConfig[device].ioTagBK1CS == hw->bk1CSPins[pindex].pin) {
                pDev->bk1CS = hw->bk1CSPins[pindex].pin;
                pDev->bk1CSAF = hw->bk1CSPins[pindex].af;
            }
            //
            // BK2
            //
            if (pConfig[device].ioTagBK2IO0 == hw->bk2IO0Pins[pindex].pin) {
                pDev->bk2IO0 = hw->bk2IO0Pins[pindex].pin;
                pDev->bk2IO0AF = hw->bk2IO0Pins[pindex].af;
            }
            if (pConfig[device].ioTagBK2IO1 == hw->bk2IO1Pins[pindex].pin) {
                pDev->bk2IO1 = hw->bk2IO1Pins[pindex].pin;
                pDev->bk2IO1AF = hw->bk2IO1Pins[pindex].af;
            }
            if (pConfig[device].ioTagBK2IO2 == hw->bk2IO2Pins[pindex].pin) {
                pDev->bk2IO2 = hw->bk2IO2Pins[pindex].pin;
                pDev->bk2IO2AF = hw->bk2IO2Pins[pindex].af;
            }
            if (pConfig[device].ioTagBK2IO3 == hw->bk2IO3Pins[pindex].pin) {
                pDev->bk2IO3 = hw->bk2IO3Pins[pindex].pin;
                pDev->bk2IO3AF = hw->bk2IO3Pins[pindex].af;
            }
            if (pConfig[device].ioTagBK2CS == hw->bk2CSPins[pindex].pin) {
                pDev->bk2CS = hw->bk2CSPins[pindex].pin;
                pDev->bk2CSAF = hw->bk2CSPins[pindex].af;
            }
        }

        if ((quadSpiConfig(device)->csFlags & QUADSPI_BK1_CS_MASK) == QUADSPI_BK1_CS_SOFTWARE) {
            pDev->bk1CS = pConfig[device].ioTagBK1CS;
        }
        if ((quadSpiConfig(device)->csFlags & QUADSPI_BK2_CS_MASK) == QUADSPI_BK2_CS_SOFTWARE) {
            pDev->bk2CS = pConfig[device].ioTagBK2CS;
        }

        bool haveResources = true;
        haveResources = haveResources && pDev->clk;

        bool needBK1 = (pConfig[device].mode == QUADSPI_MODE_DUAL_FLASH) || (pConfig[device].mode == QUADSPI_MODE_BK1_ONLY);
        if (needBK1) {
            bool haveBK1Resources = pDev->bk1IO0 && pDev->bk1IO1 && pDev->bk1IO2 && pDev->bk1IO3 && pDev->bk1CS;
            haveResources = haveResources && haveBK1Resources;
        }

        bool needBK2 = (pConfig[device].mode == QUADSPI_MODE_DUAL_FLASH) || (pConfig[device].mode == QUADSPI_MODE_BK2_ONLY);
        if (needBK2) {
            bool haveBK2Resources = pDev->bk2IO0 && pDev->bk2IO1 && pDev->bk2IO2 && pDev->bk2IO3;
            haveResources = haveResources && haveBK2Resources;
        }

        if ((needBK1 && !pDev->bk1CS) || (needBK2 && ((quadSpiConfig(device)->csFlags & QUADSPI_BK2_CS_MASK) && !pDev->bk2CS))) {
            continue;
        }

        if (haveResources) {
            pDev->dev = hw->reg;
            pDev->rcc = hw->rcc;
        }
    }
}

static void Error_Handler(void) { while (1); }

void quadSpiInitDevice(quadSpiDevice_e device)
{
    quadSpiDevice_t *quadSpi = &(quadSpiDevice[device]);

    // Enable QUADSPI clock
    RCC_ClockCmd(quadSpi->rcc, ENABLE);
    RCC_ResetCmd(quadSpi->rcc, ENABLE);

    IOInit(IOGetByTag(quadSpi->clk),  OWNER_QUADSPI_CLK,  RESOURCE_INDEX(device));
    IOInit(IOGetByTag(quadSpi->bk1IO0), OWNER_QUADSPI_BK1IO0, RESOURCE_INDEX(device));
    IOInit(IOGetByTag(quadSpi->bk1IO1), OWNER_QUADSPI_BK1IO1, RESOURCE_INDEX(device));
    IOInit(IOGetByTag(quadSpi->bk1IO2), OWNER_QUADSPI_BK1IO2, RESOURCE_INDEX(device));
    IOInit(IOGetByTag(quadSpi->bk1IO3), OWNER_QUADSPI_BK1IO3, RESOURCE_INDEX(device));
    IOInit(IOGetByTag(quadSpi->bk1CS), OWNER_QUADSPI_BK1CS, RESOURCE_INDEX(device));

    IOInit(IOGetByTag(quadSpi->bk2IO0), OWNER_QUADSPI_BK2IO0, RESOURCE_INDEX(device));
    IOInit(IOGetByTag(quadSpi->bk2IO1), OWNER_QUADSPI_BK2IO1, RESOURCE_INDEX(device));
    IOInit(IOGetByTag(quadSpi->bk2IO2), OWNER_QUADSPI_BK2IO2, RESOURCE_INDEX(device));
    IOInit(IOGetByTag(quadSpi->bk2IO3), OWNER_QUADSPI_BK2IO3, RESOURCE_INDEX(device));
    IOInit(IOGetByTag(quadSpi->bk2CS), OWNER_QUADSPI_BK2CS, RESOURCE_INDEX(device));

#if defined(STM32H7) || defined(STM32G4)
    IOConfigGPIOAF(IOGetByTag(quadSpi->clk), QUADSPI_IO_AF_CLK_CFG, quadSpi->clkAF);
    IOConfigGPIOAF(IOGetByTag(quadSpi->bk1IO0), QUADSPI_IO_AF_BK_IO_CFG, quadSpi->bk1IO0AF);
    IOConfigGPIOAF(IOGetByTag(quadSpi->bk1IO1), QUADSPI_IO_AF_BK_IO_CFG, quadSpi->bk1IO1AF);
    IOConfigGPIOAF(IOGetByTag(quadSpi->bk1IO2), QUADSPI_IO_AF_BK_IO_CFG, quadSpi->bk1IO2AF);
    IOConfigGPIOAF(IOGetByTag(quadSpi->bk1IO3), QUADSPI_IO_AF_BK_IO_CFG, quadSpi->bk1IO3AF);
    IOConfigGPIOAF(IOGetByTag(quadSpi->bk2IO0), QUADSPI_IO_AF_BK_IO_CFG, quadSpi->bk2IO0AF);
    IOConfigGPIOAF(IOGetByTag(quadSpi->bk2IO1), QUADSPI_IO_AF_BK_IO_CFG, quadSpi->bk2IO1AF);
    IOConfigGPIOAF(IOGetByTag(quadSpi->bk2IO2), QUADSPI_IO_AF_BK_IO_CFG, quadSpi->bk2IO2AF);
    IOConfigGPIOAF(IOGetByTag(quadSpi->bk2IO3), QUADSPI_IO_AF_BK_IO_CFG, quadSpi->bk2IO3AF);

    if ((quadSpiConfig(device)->csFlags & QUADSPI_BK1_CS_MASK) == QUADSPI_BK1_CS_HARDWARE) {
        IOConfigGPIOAF(IOGetByTag(quadSpi->bk1CS), QUADSPI_IO_AF_BK_CS_CFG, quadSpi->bk1CSAF);
    } else {
        IOConfigGPIO(IOGetByTag(quadSpi->bk1CS), QUADSPI_IO_BK_CS_CFG);
    }

    if ((quadSpiConfig(device)->csFlags & QUADSPI_BK2_CS_MASK) == QUADSPI_BK2_CS_HARDWARE) {
        IOConfigGPIOAF(IOGetByTag(quadSpi->bk2CS), QUADSPI_IO_AF_BK_CS_CFG, quadSpi->bk2CSAF);
    } else {
        IOConfigGPIO(IOGetByTag(quadSpi->bk2CS), QUADSPI_IO_BK_CS_CFG);
    }
#endif

    quadSpi->hquadSpi.Instance = quadSpi->dev;
    // DeInit QUADSPI hardware
    HAL_QSPI_DeInit(&quadSpi->hquadSpi);

    quadSpi->hquadSpi.Init.ClockPrescaler = QUADSPI_CLOCK_INITIALISATION;
    quadSpi->hquadSpi.Init.FifoThreshold = 1;
    quadSpi->hquadSpi.Init.SampleShifting = QSPI_SAMPLE_SHIFTING_NONE;
    quadSpi->hquadSpi.Init.FlashSize = 23; // address bits + 1
    quadSpi->hquadSpi.Init.ChipSelectHighTime = QSPI_CS_HIGH_TIME_1_CYCLE;
    quadSpi->hquadSpi.Init.ClockMode = QSPI_CLOCK_MODE_0;

    switch (quadSpiConfig(device)->mode) {
    case QUADSPI_MODE_BK1_ONLY:
        quadSpi->hquadSpi.Init.FlashID = QSPI_FLASH_ID_1;
        quadSpi->hquadSpi.Init.DualFlash = QSPI_DUALFLASH_DISABLE;
        break;
    case QUADSPI_MODE_BK2_ONLY:
        quadSpi->hquadSpi.Init.FlashID = QSPI_FLASH_ID_2;
        quadSpi->hquadSpi.Init.DualFlash = QSPI_DUALFLASH_DISABLE;
        break;
    case QUADSPI_MODE_DUAL_FLASH:
        quadSpi->hquadSpi.Init.DualFlash = QSPI_DUALFLASH_ENABLE;
        break;
    }

    // Init QUADSPI hardware
    if (HAL_QSPI_Init(&quadSpi->hquadSpi) != HAL_OK)
    {
      Error_Handler();
    }
}

// HAL doesn't support QSPI DMA
void quadSpiInitBusDMA(busDevice_t *bus)
{
    bus->useDMA = false;
}

static const uint32_t quadSpi_addressSizeMap[] = {
    QSPI_ADDRESS_8_BITS,
    QSPI_ADDRESS_16_BITS,
    QSPI_ADDRESS_24_BITS,
    QSPI_ADDRESS_32_BITS
};

static uint32_t quadSpi_addressSizeFromValue(uint8_t addressSize)
{
    // TODO addressSize + 7) / 8)
    return quadSpi_addressSizeMap[((addressSize + 1) / 8) - 1]; // rounds to nearest QSPI_ADDRESS_* value that will hold the address.
}

/**
 * Return true if the bus is currently in the middle of a transmission.
 */
LOCAL_UNUSED_FUNCTION static bool quadSpiIsBusBusy(QUADSPI_TypeDef *instance)
{
    quadSpiDevice_e device = quadSpiDeviceByInstance(instance);
    if(quadSpiDevice[device].hquadSpi.State == HAL_QSPI_STATE_BUSY)
        return true;
    else
        return false;
}

#define QUADSPI_DEFAULT_TIMEOUT 10

static void quadSpiSelectDevice(QUADSPI_TypeDef *instance)
{
    quadSpiDevice_e device = quadSpiDeviceByInstance(instance);

    IO_t bk1CS = IOGetByTag(quadSpiDevice[device].bk1CS);
    IO_t bk2CS = IOGetByTag(quadSpiDevice[device].bk2CS);

    switch(quadSpiConfig(device)->mode) {
    case QUADSPI_MODE_DUAL_FLASH:
        if ((quadSpiConfig(device)->csFlags & QUADSPI_BK1_CS_MASK) == QUADSPI_BK1_CS_SOFTWARE) {
            IOLo(bk1CS);
        }
        if ((quadSpiConfig(device)->csFlags & QUADSPI_BK2_CS_MASK) == QUADSPI_BK2_CS_SOFTWARE) {
            IOLo(bk2CS);
        }
        break;
    case QUADSPI_MODE_BK1_ONLY:
        if ((quadSpiConfig(device)->csFlags & QUADSPI_BK1_CS_MASK) == QUADSPI_BK1_CS_SOFTWARE) {
            IOLo(bk1CS);
        }
        break;
    case QUADSPI_MODE_BK2_ONLY:
        if ((quadSpiConfig(device)->csFlags & QUADSPI_BK2_CS_MASK) == QUADSPI_BK2_CS_SOFTWARE) {
            IOLo(bk2CS);
        }
        break;
    }
}

static void quadSpiDeselectDevice(QUADSPI_TypeDef *instance)
{
    quadSpiDevice_e device = quadSpiDeviceByInstance(instance);

    IO_t bk1CS = IOGetByTag(quadSpiDevice[device].bk1CS);
    IO_t bk2CS = IOGetByTag(quadSpiDevice[device].bk2CS);

    switch(quadSpiConfig(device)->mode) {
    case QUADSPI_MODE_DUAL_FLASH:
        if ((quadSpiConfig(device)->csFlags & QUADSPI_BK1_CS_MASK) == QUADSPI_BK1_CS_SOFTWARE) {
            IOHi(bk1CS);
        }
        if ((quadSpiConfig(device)->csFlags & QUADSPI_BK2_CS_MASK) == QUADSPI_BK2_CS_SOFTWARE) {
            IOHi(bk2CS);
        }
        break;
    case QUADSPI_MODE_BK1_ONLY:
        if ((quadSpiConfig(device)->csFlags & QUADSPI_BK1_CS_MASK) == QUADSPI_BK1_CS_SOFTWARE) {
            IOHi(bk1CS);
        }
        break;
    case QUADSPI_MODE_BK2_ONLY:
        if ((quadSpiConfig(device)->csFlags & QUADSPI_BK2_CS_MASK) == QUADSPI_BK2_CS_SOFTWARE) {
            IOHi(bk2CS);
        }
        break;
    }
}

bool quadSpiTransmit1LINE(const extDevice_t *dev, uint8_t instruction, uint8_t dummyCycles, const uint8_t *out, int length)
{
    quadSpiDevice_e device = quadSpiDeviceByInstance(dev->bus->busType_u.qspi.instance);
    HAL_StatusTypeDef status;

    QSPI_CommandTypeDef cmd;
    cmd.InstructionMode   = QSPI_INSTRUCTION_1_LINE;
    cmd.AddressMode       = QSPI_ADDRESS_NONE;
    cmd.AlternateByteMode = QSPI_ALTERNATE_BYTES_NONE;
    cmd.DataMode          = QSPI_DATA_NONE;
    cmd.DummyCycles       = dummyCycles;
    cmd.DdrMode           = QSPI_DDR_MODE_DISABLE;
    cmd.DdrHoldHalfCycle  = QSPI_DDR_HHC_ANALOG_DELAY;
    cmd.SIOOMode          = QSPI_SIOO_INST_EVERY_CMD;

    cmd.Instruction       = instruction;
    cmd.NbData            = length;

    if (out) {
        cmd.DataMode          = QSPI_DATA_1_LINE;
    }

    quadSpiSelectDevice(dev->bus->busType_u.qspi.instance);

    status = HAL_QSPI_Command(&quadSpiDevice[device].hquadSpi, &cmd, QUADSPI_DEFAULT_TIMEOUT);
    bool timeout = (status != HAL_OK);
    if (!timeout) {
        if (out && length > 0) {
            status = HAL_QSPI_Transmit(&quadSpiDevice[device].hquadSpi, (uint8_t *)out, QUADSPI_DEFAULT_TIMEOUT);
            timeout = (status != HAL_OK);
        }
    }

    quadSpiDeselectDevice(dev->bus->busType_u.qspi.instance);

    if (timeout) {
        quadSpiTimeoutUserCallback(dev->bus->busType_u.qspi.instance);
        return false;
    }

    return true;
}

bool quadSpiReceive1LINE(const extDevice_t *dev, uint8_t instruction, uint8_t dummyCycles, uint8_t *in, int length)
{
    quadSpiDevice_e device = quadSpiDeviceByInstance(dev->bus->busType_u.qspi.instance);
    HAL_StatusTypeDef status;

    QSPI_CommandTypeDef cmd;
    cmd.InstructionMode   = QSPI_INSTRUCTION_1_LINE;
    cmd.AddressMode       = QSPI_ADDRESS_NONE;
    cmd.AlternateByteMode = QSPI_ALTERNATE_BYTES_NONE;
    cmd.DataMode          = QSPI_DATA_1_LINE;
    cmd.DummyCycles       = dummyCycles;
    cmd.DdrMode           = QSPI_DDR_MODE_DISABLE;
    cmd.DdrHoldHalfCycle  = QSPI_DDR_HHC_ANALOG_DELAY;
    cmd.SIOOMode          = QSPI_SIOO_INST_EVERY_CMD;

    cmd.Instruction       = instruction;
    cmd.NbData            = length;

    quadSpiSelectDevice(dev->bus->busType_u.qspi.instance);

    status = HAL_QSPI_Command(&quadSpiDevice[device].hquadSpi, &cmd, QUADSPI_DEFAULT_TIMEOUT);
    bool timeout = (status != HAL_OK);
    if (!timeout) {
        status = HAL_QSPI_Receive(&quadSpiDevice[device].hquadSpi, in, QUADSPI_DEFAULT_TIMEOUT);

        timeout = (status != HAL_OK);
    }

    quadSpiDeselectDevice(dev->bus->busType_u.qspi.instance);

    if (timeout) {
        quadSpiTimeoutUserCallback(dev->bus->busType_u.qspi.instance);
        return false;
    }

    return true;
}

bool quadSpiReceive4LINES(const extDevice_t *dev, uint8_t instruction, uint8_t dummyCycles, uint8_t *in, int length)
{
    quadSpiDevice_e device = quadSpiDeviceByInstance(dev->bus->busType_u.qspi.instance);
    HAL_StatusTypeDef status;

    QSPI_CommandTypeDef cmd;
    cmd.InstructionMode   = QSPI_INSTRUCTION_4_LINES;
    cmd.AddressMode       = QSPI_ADDRESS_NONE;
    cmd.AlternateByteMode = QSPI_ALTERNATE_BYTES_NONE;
    cmd.DataMode          = QSPI_DATA_4_LINES;
    cmd.DummyCycles       = dummyCycles;
    cmd.DdrMode           = QSPI_DDR_MODE_DISABLE;
    cmd.DdrHoldHalfCycle  = QSPI_DDR_HHC_ANALOG_DELAY;
    cmd.SIOOMode          = QSPI_SIOO_INST_EVERY_CMD;

    cmd.Instruction       = instruction;
    cmd.NbData            = length;

    quadSpiSelectDevice(dev->bus->busType_u.qspi.instance);

    status = HAL_QSPI_Command(&quadSpiDevice[device].hquadSpi, &cmd, QUADSPI_DEFAULT_TIMEOUT);
    bool timeout = (status != HAL_OK);
    if (!timeout) {
        status = HAL_QSPI_Receive(&quadSpiDevice[device].hquadSpi, in, QUADSPI_DEFAULT_TIMEOUT);

        timeout = (status != HAL_OK);
    }

    quadSpiDeselectDevice(dev->bus->busType_u.qspi.instance);

    if (timeout) {
        quadSpiTimeoutUserCallback(dev->bus->busType_u.qspi.instance);
        return false;
    }

    return true;
}

bool quadSpiReceiveWithAddress1LINE(const extDevice_t *dev, uint8_t instruction, uint8_t dummyCycles, uint32_t address, uint8_t addressSize, uint8_t *in, int length)
{
    quadSpiDevice_e device = quadSpiDeviceByInstance(dev->bus->busType_u.qspi.instance);
    HAL_StatusTypeDef status;

    QSPI_CommandTypeDef cmd;
    cmd.InstructionMode   = QSPI_INSTRUCTION_1_LINE;
    cmd.AddressMode       = QSPI_ADDRESS_1_LINE;
    cmd.AlternateByteMode = QSPI_ALTERNATE_BYTES_NONE;
    cmd.DataMode          = QSPI_DATA_1_LINE;
    cmd.DummyCycles       = dummyCycles;
    cmd.DdrMode           = QSPI_DDR_MODE_DISABLE;
    cmd.DdrHoldHalfCycle  = QSPI_DDR_HHC_ANALOG_DELAY;
    cmd.SIOOMode          = QSPI_SIOO_INST_EVERY_CMD;

    cmd.Instruction       = instruction;
    cmd.Address           = address;
    cmd.AddressSize       = quadSpi_addressSizeFromValue(addressSize);
    cmd.NbData            = length;

    quadSpiSelectDevice(dev->bus->busType_u.qspi.instance);

    status = HAL_QSPI_Command(&quadSpiDevice[device].hquadSpi, &cmd, QUADSPI_DEFAULT_TIMEOUT);
    bool timeout = (status != HAL_OK);
    if (!timeout) {
        status = HAL_QSPI_Receive(&quadSpiDevice[device].hquadSpi, in, QUADSPI_DEFAULT_TIMEOUT);
        timeout = (status != HAL_OK);
    }

    quadSpiDeselectDevice(dev->bus->busType_u.qspi.instance);

    if (timeout) {
        quadSpiTimeoutUserCallback(dev->bus->busType_u.qspi.instance);
        return false;
    }

    return true;
}

bool quadSpiReceiveWithAddress4LINES(const extDevice_t *dev, uint8_t instruction, uint8_t dummyCycles, uint32_t address, uint8_t addressSize, uint8_t *in, int length)
{
    quadSpiDevice_e device = quadSpiDeviceByInstance(dev->bus->busType_u.qspi.instance);
    HAL_StatusTypeDef status;

    QSPI_CommandTypeDef cmd;
    cmd.InstructionMode   = QSPI_INSTRUCTION_1_LINE;
    cmd.AddressMode       = QSPI_ADDRESS_1_LINE;
    cmd.AlternateByteMode = QSPI_ALTERNATE_BYTES_NONE;
    cmd.DataMode          = QSPI_DATA_4_LINES;
    cmd.DummyCycles       = dummyCycles;
    cmd.DdrMode           = QSPI_DDR_MODE_DISABLE;
    cmd.DdrHoldHalfCycle  = QSPI_DDR_HHC_ANALOG_DELAY;
    cmd.SIOOMode          = QSPI_SIOO_INST_EVERY_CMD;

    cmd.Instruction       = instruction;
    cmd.Address           = address;
    cmd.AddressSize       = quadSpi_addressSizeFromValue(addressSize);
    cmd.NbData            = length;

    quadSpiSelectDevice(dev->bus->busType_u.qspi.instance);

    status = HAL_QSPI_Command(&quadSpiDevice[device].hquadSpi, &cmd, QUADSPI_DEFAULT_TIMEOUT);
    bool timeout = (status != HAL_OK);
    if (!timeout) {
        status = HAL_QSPI_Receive(&quadSpiDevice[device].hquadSpi, in, QUADSPI_DEFAULT_TIMEOUT);
        timeout = (status != HAL_OK);
    }

    quadSpiDeselectDevice(dev->bus->busType_u.qspi.instance);

    if (timeout) {
        quadSpiTimeoutUserCallback(dev->bus->busType_u.qspi.instance);
        return false;
    }

    return true;
}
bool quadSpiTransmitWithAddress1LINE(const extDevice_t *dev, uint8_t instruction, uint8_t dummyCycles, uint32_t address, uint8_t addressSize, const uint8_t *out, int length)
{
    quadSpiDevice_e device = quadSpiDeviceByInstance(dev->bus->busType_u.qspi.instance);
    HAL_StatusTypeDef status;

    QSPI_CommandTypeDef cmd;
    cmd.InstructionMode   = QSPI_INSTRUCTION_1_LINE;
    cmd.AddressMode       = QSPI_ADDRESS_1_LINE;
    cmd.AlternateByteMode = QSPI_ALTERNATE_BYTES_NONE;
    cmd.DataMode          = QSPI_DATA_1_LINE;
    cmd.DummyCycles       = dummyCycles;
    cmd.DdrMode           = QSPI_DDR_MODE_DISABLE;
    cmd.DdrHoldHalfCycle  = QSPI_DDR_HHC_ANALOG_DELAY;
    cmd.SIOOMode          = QSPI_SIOO_INST_EVERY_CMD;

    cmd.Instruction       = instruction;
    cmd.Address           = address;
    cmd.AddressSize       = quadSpi_addressSizeFromValue(addressSize);
    cmd.NbData            = length;

    quadSpiSelectDevice(dev->bus->busType_u.qspi.instance);

    status = HAL_QSPI_Command(&quadSpiDevice[device].hquadSpi, &cmd, QUADSPI_DEFAULT_TIMEOUT);
    bool timeout = (status != HAL_OK);

    if (!timeout) {
        status = HAL_QSPI_Transmit(&quadSpiDevice[device].hquadSpi, (uint8_t *)out, QUADSPI_DEFAULT_TIMEOUT);
        timeout = (status != HAL_OK);
    }

    quadSpiDeselectDevice(dev->bus->busType_u.qspi.instance);

    if (timeout) {
        quadSpiTimeoutUserCallback(dev->bus->busType_u.qspi.instance);
        return false;
    }

    return true;
}

bool quadSpiTransmitWithAddress4LINES(const extDevice_t *dev, uint8_t instruction, uint8_t dummyCycles, uint32_t address, uint8_t addressSize, const uint8_t *out, int length)
{
    quadSpiDevice_e device = quadSpiDeviceByInstance(dev->bus->busType_u.qspi.instance);
    HAL_StatusTypeDef status;

    QSPI_CommandTypeDef cmd;
    cmd.InstructionMode   = QSPI_INSTRUCTION_1_LINE;
    cmd.AddressMode       = QSPI_ADDRESS_1_LINE;
    cmd.AlternateByteMode = QSPI_ALTERNATE_BYTES_NONE;
    cmd.DataMode          = QSPI_DATA_4_LINES;
    cmd.DummyCycles       = dummyCycles;
    cmd.DdrMode           = QSPI_DDR_MODE_DISABLE;
    cmd.DdrHoldHalfCycle  = QSPI_DDR_HHC_ANALOG_DELAY;
    cmd.SIOOMode          = QSPI_SIOO_INST_EVERY_CMD;

    cmd.Instruction       = instruction;
    cmd.Address           = address;
    cmd.AddressSize       = quadSpi_addressSizeFromValue(addressSize);
    cmd.NbData            = length;

    quadSpiSelectDevice(dev->bus->busType_u.qspi.instance);

    status = HAL_QSPI_Command(&quadSpiDevice[device].hquadSpi, &cmd, QUADSPI_DEFAULT_TIMEOUT);
    bool timeout = (status != HAL_OK);

    if (!timeout) {
        status = HAL_QSPI_Transmit(&quadSpiDevice[device].hquadSpi, (uint8_t *)out, QUADSPI_DEFAULT_TIMEOUT);
        timeout = (status != HAL_OK);
    }

    quadSpiDeselectDevice(dev->bus->busType_u.qspi.instance);

    if (timeout) {
        quadSpiTimeoutUserCallback(dev->bus->busType_u.qspi.instance);
        return false;
    }

    return true;
}

bool quadSpiInstructionWithAddress1LINE(const extDevice_t *dev, uint8_t instruction, uint8_t dummyCycles, uint32_t address, uint8_t addressSize)
{
    quadSpiDevice_e device = quadSpiDeviceByInstance(dev->bus->busType_u.qspi.instance);
    HAL_StatusTypeDef status;

    QSPI_CommandTypeDef cmd;
    cmd.InstructionMode   = QSPI_INSTRUCTION_1_LINE;
    cmd.AddressMode       = QSPI_ADDRESS_1_LINE;
    cmd.AlternateByteMode = QSPI_ALTERNATE_BYTES_NONE;
    cmd.DataMode          = QSPI_DATA_NONE;
    cmd.DummyCycles       = dummyCycles;
    cmd.DdrMode           = QSPI_DDR_MODE_DISABLE;
    cmd.DdrHoldHalfCycle  = QSPI_DDR_HHC_ANALOG_DELAY;
    cmd.SIOOMode          = QSPI_SIOO_INST_EVERY_CMD;

    cmd.Instruction       = instruction;
    cmd.Address           = address;
    cmd.AddressSize       = quadSpi_addressSizeFromValue(addressSize);
    cmd.NbData            = 0;

    quadSpiSelectDevice(dev->bus->busType_u.qspi.instance);

    status = HAL_QSPI_Command(&quadSpiDevice[device].hquadSpi, &cmd, QUADSPI_DEFAULT_TIMEOUT);
    bool timeout = (status != HAL_OK);

    quadSpiDeselectDevice(dev->bus->busType_u.qspi.instance);

    if (timeout) {
        quadSpiTimeoutUserCallback(dev->bus->busType_u.qspi.instance);
        return false;
    }

    return true;
}

bool quadSpiInstructionWithData1LINE(const extDevice_t *dev, uint8_t instruction, uint8_t dummyCycles, const uint8_t *out, int length)
{
    quadSpiDevice_e device = quadSpiDeviceByInstance(dev->bus->busType_u.qspi.instance);
    HAL_StatusTypeDef status;

    QSPI_CommandTypeDef cmd;
    cmd.InstructionMode   = QSPI_INSTRUCTION_1_LINE;
    cmd.AddressMode       = QSPI_ADDRESS_NONE;
    cmd.AlternateByteMode = QSPI_ALTERNATE_BYTES_NONE;
    cmd.DataMode          = QSPI_DATA_1_LINE;
    cmd.DummyCycles       = dummyCycles;
    cmd.DdrMode           = QSPI_DDR_MODE_DISABLE;
    cmd.DdrHoldHalfCycle  = QSPI_DDR_HHC_ANALOG_DELAY;
    cmd.SIOOMode          = QSPI_SIOO_INST_EVERY_CMD;

    cmd.Instruction       = instruction;
    cmd.NbData            = length;

    quadSpiSelectDevice(dev->bus->busType_u.qspi.instance);

    status = HAL_QSPI_Command(&quadSpiDevice[device].hquadSpi, &cmd, QUADSPI_DEFAULT_TIMEOUT);
    bool timeout =(status != HAL_OK);

    if (!timeout) {
        status = HAL_QSPI_Transmit(&quadSpiDevice[device].hquadSpi, (uint8_t *)out, QUADSPI_DEFAULT_TIMEOUT);
        timeout = (status != HAL_OK);
    }

    quadSpiDeselectDevice(dev->bus->busType_u.qspi.instance);

    if (timeout) {
        quadSpiTimeoutUserCallback(dev->bus->busType_u.qspi.instance);
        return false;
    }

    return true;
}

void quadSpiSetDivisor(const extDevice_t *dev, uint16_t divisor)
{
    quadSpiDevice_e device = quadSpiDeviceByInstance(dev->bus->busType_u.qspi.instance);
    if (HAL_QSPI_DeInit(&quadSpiDevice[device].hquadSpi) != HAL_OK)
    {
        Error_Handler();
    }

    quadSpiDevice_t *quadSpi = &(quadSpiDevice[device]);

    quadSpi->hquadSpi.Init.ClockPrescaler = divisor;

    HAL_QSPI_Init(&quadSpi->hquadSpi);
}

void quadSpiWait(const extDevice_t *dev)
{
    UNUSED(dev);

    // Return immediately as this implementation is blocking
}

void quadSpiSequence(const extDevice_t *dev, busSegment_t *segments)
{
    if (!dev || !dev->bus) {
        return;
    }

    quadSpiDevice_e device = quadSpiDeviceByInstance(dev->bus->busType_u.qspi.instance);

    busSegment_t *curSegment = segments;

    bool csNegated = true;

    while (curSegment->len) {
        if (csNegated) {
            quadSpiSelectDevice(dev->bus->busType_u.qspi.instance);
            csNegated = false;
        }

        if (curSegment->u.buffers.txData) {
            /* Configure QSPI: DLR register with the number of data to write */
            WRITE_REG(quadSpiDevice[device].hquadSpi.Instance->DLR, curSegment->len - 1U);
            HAL_QSPI_Transmit(&quadSpiDevice[device].hquadSpi, (uint8_t *)curSegment->u.buffers.txData, QUADSPI_DEFAULT_TIMEOUT);
        }
        if (curSegment->u.buffers.rxData) {
            /* Configure QSPI: DLR register with the number of data to read */
            WRITE_REG(quadSpiDevice[device].hquadSpi.Instance->DLR, curSegment->len - 1U);
            HAL_QSPI_Receive(&quadSpiDevice[device].hquadSpi, (uint8_t *)curSegment->u.buffers.rxData, QUADSPI_DEFAULT_TIMEOUT);
        }

        if (curSegment->negateCS) {
            quadSpiDeselectDevice(dev->bus->busType_u.qspi.instance);
            csNegated = true;
        }

        // Advance to the next segment
        curSegment++;
    }

    if (!csNegated) {
        quadSpiDeselectDevice(dev->bus->busType_u.qspi.instance);
    }
}
#endif
